#pragma once
#include "fiber.h"
#include "spinlock_fiber_queue.h"

class scheduler
{
public:
	typedef spinlock_fiber_queue<spinlock_fiber_queue_name::scheduler_running_queue>
		running_fiber_queue_t;

	scheduler()
		:is_stop_(true)
	{
		
	}
	~scheduler()
	{

	}
	template<typename F, typename ...Args>
	void append_fiber(F &f, Args &...args)
	{
		fiber::ptr_t _fiber = fiber::create_fiber(f, args...);
		append_fiber(_fiber);
	}
	void append_fiber(typename fiber::ptr_t _fiber)
	{
		auto status = _fiber->get_status ();
		if (status ==  fiber::status::e_init || 
			status == fiber::status::e_running)
		{
			_fiber->attach_scheduler (this);
			running_fiber_queue_.push_back (_fiber);
		}
	}
	void start()
	{
		is_stop_ = false;
		worker_ = std::thread([=] {
			current_scheduler() = this;
			scheduler_fiber_ = ConvertThreadToFiber(NULL);
			run ();
			ConvertFiberToThread();
		});
	}
	static scheduler *&current_scheduler()
	{
		static thread_local scheduler *s = nullptr;
		return s;
	}
	void stop()
	{
		if (is_stop_)
			return;
		is_stop_ = true;
		worker_.join();
	}
	std::size_t get_fiber_size()
	{
		return running_fiber_queue_.size() + 
			waiting_fiber_queue_.size();
	}
	void erase_fiber(fiber::ptr_t _fiber)
	{
		if (_fiber->get_status() == fiber::status::e_wait)
		{
			waiting_fiber_queue_.erase(_fiber->get_waiting_queue_iterator());
		}
		else
		{

		}
	}
	fiber::ptr_t get_current_schedule_fiber()
	{
		return current_schedule_fiber_.lock();
	}
private:
	void run ()
	{
		while (is_stop_ == false)
		{
			running_fiber_queue_t remain_fiber_queue;
			while (running_fiber_queue_.size () && is_stop_ == false)
			{
				fiber::ptr_t fiber_ = running_fiber_queue_.front();
				running_fiber_queue_.pop_front ();

				current_schedule_fiber_ = fiber_;
				fiber_->switch_to_fiber ();

				if (fiber_->get_status() == fiber::status::e_running)
				{
					remain_fiber_queue.push_back(fiber_);
				}
				else if (fiber_->get_status() == fiber::status::e_wait)
				{
					fiber_->set_waiting_queue_iterator( waiting_fiber_queue_.insert(
						std::make_pair(fiber_->get_schedule_timepoint(), fiber_)));
				}
			}
			if (is_stop_)
				return;
			if(remain_fiber_queue.size())
				running_fiber_queue_.swap (remain_fiber_queue);
			
			schedule_waiting_fiber ();
			wait_for_fiber ();
		}
		
	}
	void schedule_waiting_fiber ()
	{
		auto now = high_resolution_clock::now ();
		while (waiting_fiber_queue_.size ())
		{
			auto item = waiting_fiber_queue_.begin();
			if (now > item->first)
			{
				item->second->set_status(fiber::status::e_running);
				running_fiber_queue_.push_back (item->second);
				waiting_fiber_queue_.erase (waiting_fiber_queue_.begin ());
			}
			else
			{
				break;
			}
		}
	}
	void wait_for_fiber ()
	{
		if (running_fiber_queue_.size ())
			return;
		auto sleep_time_point = high_resolution_clock::now () + 
			high_resolution_clock::duration (std::chrono::milliseconds (10000));
		if (waiting_fiber_queue_.size())
		{
			sleep_time_point = waiting_fiber_queue_.begin ()->first;
		}
		std::unique_lock<running_fiber_queue_t::mutex_t> 
			lock( running_fiber_queue_.get_mutex());
		cv_.wait_until(lock, sleep_time_point, [this] { 
			return running_fiber_queue_.size() || is_stop_; });

	}

	running_fiber_queue_t running_fiber_queue_;

	std::multimap<high_resolution_clock::time_point, fiber::ptr_t> waiting_fiber_queue_;
	std::atomic_bool is_stop_;
	std::thread worker_;
	void *scheduler_fiber_;
	fiber::wptr_t current_schedule_fiber_;
	std::condition_variable_any cv_;
};
